package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gitee.com/dark.H/Light/utils"

	"github.com/atotto/clipboard"
	pr "github.com/c-bata/go-prompt"
	"github.com/manifoldco/promptui"
	pu "github.com/manifoldco/promptui"
)

var (
	TIMEOUT  = flag.Duration("t", 0, "Erase clipboard after timeout.  Durations are specified like \"20s\" or \"2h45m\".  0 (default) means never erase.")
	COPYMODE = flag.Bool("c", false, "set true is copy mode")
	GREPCP   = flag.Bool("g", false, "set grep and paste")
	SEAFILE  = flag.Bool("f", false, "true to search in current")
	HIST     = flag.Bool("H", false, "true to search history")
	MSF      = flag.Bool("m", false, "true to generate msf payload")
)

func main() {
	flag.Parse()
	//先取程序的标准输入属性信息
	// if info, _ := os.Stdin.Stat(); info.Size() > 0 {
	// 	buf, _ := ioutil.ReadAll(os.Stdin)
	// 	CopyAll(buf)
	// 	os.Exit(0)
	// }

	if *SEAFILE {
		SearchFile(".")
		return
	}
	if *MSF {
		utils.GeneratePayload(utils.Choose())
		return
	}
	if *HIST {
		// hos, err := exec.LookPath("history")
		if runtime.GOOS == "linux" {
			out, err := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".bash_history"))

			lines := Lines(string(out))
			if out2, err2 := ioutil.ReadFile(filepath.Join(os.Getenv("HOME"), ".zsh_history")); err2 == nil {
				for _, l := range bytes.Split(out2, []byte("\n")) {
					if strings.TrimSpace(string(l)) == "" {
						continue
					}
					if len(l) < 14 {
						// fmt.Println(l)
						continue
					}
					lines = append(lines, string(l)[14:])
				}
			}
			if err != nil {
				log.Println(err)
				return
			}
			// out, err := exec.Command(shell, "-c", "history").Output()
			// fmt.Println(string(out))

			line := SelectionLine(lines)
			if !clipboard.Unsupported {
				clipboard.WriteAll(line)
			} else {
				fmt.Println(line)
			}
		}
		return
	}
	// 判断标准输入设备属性 os.ModeCharDevice 是否设置
	// 同时判断是否有数据输入
	// iouilt.ReadAll()
	if *COPYMODE {
		CopyAll(nil)
		// fmt.Println("<<<<<")
		// cm, err := exec.LookPath(os.Args[0])
		// if err != nil {
		// 	fmt.Println(err)
		// 	return
		// }
		// o, er := exec.Command(cm).Output()
		// fmt.Println(string(o), er)
	} else if *GREPCP {
		GrepSet()
	} else {
		PasteLine()
	}

}

func PasteLine() {
	buf, err := clipboard.ReadAll()
	if err != nil {
		panic(err)
	}

	line := SelectionLine(Lines(buf))
	clipboard.WriteAll(line)
	fmt.Println("use :", line)
}

func CopyAll(out []byte) {
	var err error
	if out == nil {
		out, err = ioutil.ReadAll(os.Stdin)
		if err != nil {
			panic(err)
		}
	}
	mm := string(out)
	// if runtime.GOOS == "windows" {
	// 	outt, _ := utf8ToGBK(out)
	// 	mm = string(outt)
	// }

	if err := clipboard.WriteAll(mm); err != nil {
		panic(err)
	}

	if TIMEOUT != nil && *TIMEOUT > 0 {
		<-time.After(*TIMEOUT)
		text, err := clipboard.ReadAll()
		if err != nil {
			os.Exit(1)
		}
		if text == string(out) {
			err = clipboard.WriteAll("")
		}
	}
	return
}

func SelectionLine(lines []string) (out string) {
	// fmt.Println(lines)
	CH := map[string]*regexp.Regexp{
		"ssh": regexp.MustCompile(`(ssh\s+?.+)`),
		"ip":  regexp.MustCompile(`(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`),
		"url": regexp.MustCompile(`(https?\:\/\/[\w\.\$\&\?\=\/]+)`),
	}
	if len(lines) > 20 {
		newLines := []string{}
		validate := func(input string) error {
			found := false
			// fmt.Println()
			c := 0
			if _, ok := CH[input]; ok {
				return nil
			}
			for _, l := range lines {
				if strings.Contains(l, input) {
					c++
					// found = true
					return nil
				}
			}
			if found {
				// pu.IconGood = pu.IconGood + fmt.Sprintf(" [%d]", c)
				return nil
			}
			return errors.New("not found")
		}

		funcMap := promptui.FuncMap
		funcMap["truncate"] = func(size int, input string) string {
			if len(input) <= size {
				return input
			}
			return input[:size-3] + "..."
		}
		prompt := promptui.Prompt{
			Label:    "🍕grep:",
			Validate: validate,
			Templates: &pu.PromptTemplates{
				Confirm: `🍕 {{ .Title | cyan | bold }}`,
				// Inactive: `   {{ .Title | cyan }}`,
				Success: `{{ "✔" | green | bold }} {{ "Recipe" | bold }}: {{ .Title | cyan }}`,
			},
		}

		result, err := prompt.Run()
		for _, l := range lines {
			if re, ok := CH[result]; ok {
				newLines = append(newLines, re.FindAllString(l, -1)...)
			} else if strings.Contains(l, result) {
				newLines = append(newLines, l)
			}
		}
		lines = newLines
		if len(lines) == 0 {
			return
		}
		if err != nil {
			fmt.Printf("Prompt failed %v\n", err)
			return
		}
	}

	ppp := pu.Select{
		Label: "Selecto Line To Do",
		Items: lines,
		Size:  20,
		Searcher: func(input string, idx int) bool {
			// recipe := res.Results[idx]
			// title := strings.ToLower(recipe.Title)

			if strings.Contains(lines[idx], input) {
				return true
			}
			// if strings.Contains(, input) {
			// 	return true
			// }
			return false
		},
		// Templates: &templates,
	}
	_, result, err := ppp.Run()
	if err != nil {
		if err == pu.ErrEOF {
			CopyAll(nil)
			fmt.Println("copyed")
			os.Exit(0)
		}
		fmt.Println(err.Error())
		fmt.Printf("tui failed %v\n", err)
		return
	}
	return result
}

func Lines(buf string) (lines []string) {
	for _, line := range strings.Split(buf, "\n") {
		if strings.TrimSpace(line) == "" {
			continue
		}
		lines = append(lines, strings.TrimSpace(line))
	}
	return lines
}

func Grep(inlines []string) (lines []string) {
	// items := make(map[string]string)
	lines = inlines
	matchLine := func(k string) []string {
		newLines := []string{}
		for _, t := range lines {
			if t == "" {
				continue
			}
			if strings.Contains(t, k) {
				newLines = append(newLines, t)
			}
		}
		// newLines
		return newLines
	}
	newLines := []string{}

	comp := func(d pr.Document) []pr.Suggest {
		s := []pr.Suggest{}
		pre := d.TextBeforeCursor()
		newLines = matchLine(pre)
		for _, l := range newLines {
			s = append(s, pr.Suggest{Text: strconv.Quote(strings.TrimSpace(l))})
		}
		return pr.FilterContains(s, pre, true)
	}
	for {
		filter := pr.Input("[<40]Grep>>", comp)
		r, _ := strconv.Unquote(filter)
		newLines = matchLine(r)
		if filter == "" {
			log.Println("exit abord")
			os.Exit(0)
		}
		if len(newLines) < 40 {
			lines = newLines
			break
		} else {
			fmt.Println(len(newLines))
		}
	}

	return

}

func GrepSet() {
	buf, _ := ioutil.ReadAll(os.Stdin)
	lines := Grep(Lines(string(buf)))
	if len(lines) == 0 {
		return
	}
	clipboard.WriteAll(lines[0])
	fmt.Println("paste  set :" + lines[0])
}

// func GetSear
func SearchFile(root string) (f string) {
	realRoot, _ := filepath.Abs(root)

	for {
		tmps := []string{"[re search]"}
		name := pr.Input(">", func(d pr.Document) (s []pr.Suggest) {
			t := d.TextBeforeCursor()
			fs, _ := ioutil.ReadDir(root)
			for _, f := range fs {
				s = append(s, pr.Suggest{Text: filepath.Join(root, f.Name()), Description: fmt.Sprint(f.IsDir())})
			}
			return pr.FilterContains(s, t, true)
		})
		if name == "exit" {
			break
		}
		filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return nil
			}
			if info.IsDir() {
				return nil
			}
			if strings.Contains(path, name) && len(name) > 1 {
				tmps = append(tmps, path)
			}
			return nil
		})
		// if err != nil {
		// 	fmt.Printf("error walking the path %q: %v\n", err)
		// 	return
		// }
		f = SelectionLine(tmps)
		if f != "[re search]" || f == "" {
			break
		}
	}
	if f != "" {
		sf := pu.Select{
			Items: []string{"copy", "open"},
		}
		_, o, e := sf.Run()
		if e != nil {
			return
		}
		if o == "copy" {
			clipboard.WriteAll(filepath.Dir(f))
		} else if o == "open" {
			// dir := "cd " + filepath.Dir(f) + ";"
			dir := filepath.Dir(filepath.Join(realRoot, f))
			os.Chdir(dir)
			f = filepath.Base(f)
			// java := "java"
			openwith := pu.Select{
				Label: "open with:",
				Items: []string{"", "java -jar", "[custom]"},
			}
			_, oo, e := openwith.Run()
			if e != nil {
				return
			}
			if oo == "[custom]" {
				dd := pu.Prompt{Label: "open with>"}
				oo, e = dd.Run()
				if e != nil {
					return
				}
			}

			// cmder := fmt.Sprintf("'%s 2>&1'", oo+" "+f)
			// pre = append(pre, f)
			// StartProgress(pre...)
			// var err error
			// pre[0], err = exec.LookPath(pre[0])
			// if err != nil {
			//      fmt.Println("not found :", pre[0])
			//      return
			// }
			ts := strings.Fields(oo)

			ts = append(ts, f)
			srun, err := exec.LookPath(ts[0])
			if err != nil {
				srun = ts[0]
			}
			if len(ts) == 1 {
				srun = filepath.Join(dir, ts[0])
			}
			ts[0] = srun

			cm := exec.Command(ts[0], ts[1:]...)
			cm.Env = os.Environ()

			// cm.Stdout = nil
			// cm.Stderr = nil
			err = cm.Start()
			if err != nil {
				fmt.Println(err, ts)
				log.Fatal(err)
			}
			// fmt.Println(string(msg))
			// fmt.Println(ts)
			time.Sleep(3 * time.Second)
			// cm.SysProcAttr
		}

		// clipboard.WriteAll(filepath.Dir(f))
	}
	return
}
